// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2024-2024.
 * Description:sysmonitor:modify inotify to save process info in inotify for sysmonitor
 * Author: hanhuihui
 * Create: 2024-4-23
 */
#include <linux/sched/mm.h>
#include "inotify.h"
#include "sysmonitor_inotify.h"

atomic_t g_fm_flag = ATOMIC_INIT(0);
struct fsnotify_group *g_fm_group;

static bool event_compare(struct fsnotify_event *old_fsn, struct fsnotify_event *new_fsn)
{
	struct inotify_event_info *old, *new;

	old = INOTIFY_E(old_fsn);
	new = INOTIFY_E(new_fsn);
	if (old->mask & FS_IN_IGNORED)
		return false;
	if ((old->mask == new->mask) &&
		(old->wd == new->wd) &&
		(old->name_len == new->name_len) &&
		(!old->name_len || !strcmp(old->name, new->name)))
		return true;
	return false;
}

static int inotify_merge(struct fsnotify_group *group, struct fsnotify_event *event)
{
	struct list_head *list = &group->notification_list;
	struct fsnotify_event *last_event;

	last_event = list_entry(list->prev, struct fsnotify_event, list);
	return event_compare(last_event, event);
}

int inotify_handle_inode_event_extend(struct fsnotify_mark *inode_mark, u32 mask,
		struct inode *inode, struct inode *dir,
		const struct qstr *name, u32 cookie)
{
	struct inotify_inode_mark *i_mark;
	struct inotify_event_info *event = NULL;
	struct fsnotify_event *fsn_event;
	struct fsnotify_group *group = inode_mark->group;
	int ret;
	int len = 0;
	int alloc_len = sizeof(struct inotify_event_info);
	struct mem_cgroup *old_memcg;
	struct inotify_event_info_extend *event_extend = NULL;
	struct inotify_event_process_info *info;
	int pad_name_len = 0;

	if (name) {
		len = name->len;
		if (len) {
			pad_name_len = roundup(len + 1, sizeof(struct inotify_event));
			alloc_len += pad_name_len;
		} else {
			alloc_len += len + 1;
		}
	}

	i_mark = container_of(inode_mark, struct inotify_inode_mark, fsn_mark);

	alloc_len += sizeof(struct inotify_event_process_info);
	old_memcg = set_active_memcg(group->memcg);
	event_extend = kmalloc(alloc_len, GFP_KERNEL_ACCOUNT | __GFP_RETRY_MAYFAIL);
	set_active_memcg(old_memcg);

	if (unlikely(!event_extend)) {
		fsnotify_queue_overflow(group);
		return -ENOMEM;
	}

	if (mask & (IN_MOVE_SELF | IN_DELETE_SELF))
		mask &= ~IN_ISDIR;

	event = &event_extend->event;
	fsn_event = &event->fse;
	fsnotify_init_event(fsn_event);
	event->mask = mask;
	event->wd = i_mark->wd;
	event->sync_cookie = cookie;
	event->name_len = len;
	if (len)
		strcpy(event->name, name->name);

	info = (struct inotify_event_process_info *)((void *)event_extend +
			sizeof(struct inotify_event_info) + pad_name_len);
	info->pid = current->pid;
	memcpy(info->comm, current->comm, TASK_COMM_LEN);
	info->parent_pid = current->parent->pid;
	memcpy(info->parent_comm, current->parent->comm, TASK_COMM_LEN);

	ret = fsnotify_add_event(group, fsn_event, inotify_merge);
	if (ret)
		fsnotify_destroy_event(group, fsn_event);

	if (inode_mark->mask & IN_ONESHOT)
		fsnotify_destroy_mark(inode_mark, group);

	return 0;
}

int is_sysmonitor_group(struct fsnotify_group *group)
{
	return group == g_fm_group;
}

void free_sysmonitor_group(struct fsnotify_group *group)
{
	if (is_sysmonitor_group(group))
		g_fm_group = NULL;
}

static inline struct inotify_event_info_extend *INOTIFY_E_EXTEND(struct inotify_event_info *event)
{
	return container_of(event, struct inotify_event_info_extend, event);
}

ssize_t copy_event_extend_to_user(struct fsnotify_event *fsn_event,
				char __user *buf, int event_size)
{
	struct inotify_event_info_extend *event_extend;
	struct inotify_event_process_info *info;
	struct inotify_event_info *event;
	int pad_name_len = 0;

	event = INOTIFY_E(fsn_event);
	event_extend = INOTIFY_E_EXTEND(event);
	if (event->name_len > 0)
		pad_name_len = roundup(event->name_len + 1, sizeof(struct inotify_event));
	buf += event_size;

	info = (struct inotify_event_process_info *)((void *)event_extend +
			sizeof(struct inotify_event_info) + pad_name_len);
	if (copy_to_user(buf, info, sizeof(struct inotify_event_process_info)))
		return -EFAULT;

	return event_size + sizeof(struct inotify_event_process_info);
}

/* g_fm_flag is added to protect g_fm_group set */
int set_sysmonitor_group_flag(struct fsnotify_group *group)
{
	int ret;

	if (atomic_cmpxchg(&g_fm_flag, 0, 1)) {
		ret = -EPERM;
	} else {
		if (g_fm_group != NULL) {
			pr_info("filemonitor private group already set.\n");
			ret = -EPERM;
		} else {
			g_fm_group = group;
			pr_info("set filemonitor private group success.\n");
			ret = 0;
		}
		atomic_set(&g_fm_flag, 0);
	}
	return ret;
}
